#include "Engine.h"

#include "../Pieces/Pawn.h"
#include "../Pieces/Rook.h"
#include "../Pieces/Knight.h"
#include "../Pieces/Bishop.h"
#include "../Pieces/Queen.h"
#include "../Pieces/King.h"

#include <sstream>
using namespace std;

namespace Core
{
	Engine * Engine::_instance = NULL;
	int Engine::_humans = 1;

	Engine::Engine(void)
	{
		init();
	}

	Engine::~Engine(void)
	{
		dispose();
	}

	ChColor Engine::whosPlaying() const
	{
		return _whosPlaying;
	}

	ChStatus Engine::status() const
	{
		return lastMove()->status();
	}

	int Engine::moveNumber() const
	{
		return _tracker->moveCount();
	}


	void Engine::dispose()
	{
		delete _board;
		delete _wPlr;
		delete _bPlr;

		delete _tracker;
	}

	Engine * Engine::instance()
	{
		if(_instance == NULL)
			_instance = new Engine();

		return _instance;
	}

	void Engine::reset()
	{
		if(_instance != NULL)
		{
			delete _instance;
			_instance = NULL;
		}
	}

	void Engine::init()
	{
		_board = new Chessboard();

		_tracker = new MoveTracker();

		// set first turn for white player
		_whosPlaying = ChWhite;
		//_whosChecked = 0;
		//_status = ChStatusNone;

		// create players
		_wPlr = new Player(ChWhite);
		_bPlr = new Player(ChBlack);

		// set square for left rook to right rook
		int c1 = 0, c2 = 0;
		for (int i = 0; i < PLR_PIECES / 2; i++)
		{
			_board->at(0, i)->piece(_wPlr->getPiece(i));
			_board->at(7, i)->piece(_bPlr->getPiece(i));
		}

		// set square for pawns
		for (int i = PLR_PIECES / 2; i < PLR_PIECES; i++)
		{
			_board->at(1, i - 8)->piece(_wPlr->getPiece(i));
			_board->at(6, i - 8)->piece(_bPlr->getPiece(i));
		}

		_wKing = _wPlr->getPiece(4);
		_bKing = _bPlr->getPiece(4);
	}

	string Engine::saveToString() const
	{
		string s = "";

		for (int i = 0; i < _tracker->moveCount(); i++)
			s = s + _tracker->at(i)->toString() + " ";

		return s;
	}

	void Engine::loadFromString(const string &s)
	{
		Engine::reset();
	
		stringstream ss;
		string str;

		ss << s;

		while(ss >> str)
		{
			if(str.length() != 4)
				throw "invalid string of game!";
			int r1 = str[0] - '0';
			int c1 = str[1] - '0';
			int r2 = str[2] - '0';
			int c2 = str[3] - '0';

			Engine::instance()->move(r1, c1, r2, c2);
		}
	}

	void Engine::move(int r0, int c0, int r1, int c1, bool analyze)
	{
		string invalidMove = "Invalid Move!";
		
		Square * src = _board->at(r0, c0);
		Square * dst = _board->at(r1, c1);

		if(src->piece() == NULL)
			throw "No peace is selected";
		
		const Move * temp =  src->piece()->canMove(dst);
		
		if(temp == NULL)
			throw invalidMove;

		Move * m = &Move(*temp);
		while(m->prevMove() != NULL)
			m = m->prevMove();

		while(1)
		{
			Move newMove(*m);
			newMove.moveNum(_tracker->moveCount());
			_tracker->newMove(newMove);
			newMove.piece()->move(newMove);

			doMove(&newMove);

			if(m->nextMove() == NULL)
				break;
			m = m->nextMove();
		}

		if(analyze)
		{
			m->status(analyzeOverallStatus(m));
			if(lastMove()->status() == ChStatus::ChStatusNone && isDrawn(m))
				m->status(ChStatus::ChDraw);
		}

		// change turn
		switchTurn();

		// delete all valid moves of pieces
		_wPlr->deleteValidMoves();
		_bPlr->deleteValidMoves();
	}

	void Engine::doMove(const Move * newMove)
	{
		//const Move * newMove = _tracker->at(num);
		Piece * p = newMove->piece();
		Piece * dstP = newMove->to()->piece();

		// do the move
		newMove->to()->piece(p);
		newMove->from()->piece(NULL);

		if(newMove->action() & ChEat)
		{
			//newMove->pieceEaten(dstP);
			// set destination piece isEaten
			dstP->setEaten(true);
		}
		if(newMove->action() & ChEnpassant)
		{
			//const Move * lastMove = _tracker->at(_tracker->moveCount()-1);
			newMove->pieceEaten()->setEaten(true);

			// remove the eaten piece from its square:
			newMove->pieceEaten()->square()->piece(NULL);
		}
		if(newMove->action() & ChPromotion)
		{
			// should ask the user what to pick?!
			p->setEaten(true); // opt out current pawn
			Piece * q = new Queen(p->player(), newMove->to());
			newMove->to()->piece(q);
			((Pawn*)p)->promotedTo(q);
			if(p->color() == ChWhite)
				_wPlr->promotes().push_back(q);
			else
				_bPlr->promotes().push_back(q);
		}
	}

	const Move * Engine::undoMove()
	{
		const Move * mv = _tracker->last();
		if(mv == NULL)
			return NULL;

		const Move * tmp = mv;
		while(tmp != NULL)
		{
			_tracker->undo();
			tmp->piece()->undoMove();
			undoMove(tmp);

			tmp = tmp->prevMove();
		}

		switchTurn();

		return mv;
	}

	const Move * Engine::redo()
	{
		const Move * mv = _tracker->at(_tracker->moveCount());
		if(mv == NULL)
			return NULL;

		
		const Move * tmp = mv;
		while(tmp != NULL)
		{
			_tracker->redo();
			tmp->piece()->redoMove();
			doMove(mv);

			tmp = tmp->nextMove();
		}

		switchTurn();

		return mv;
	}

	void Engine::undoMove(const Move * mv)
	{		
		// return the moved piece into it's previous square
		mv->to()->piece(NULL);
		mv->from()->piece(mv->piece());

		if((mv->action() & ChEat) || (mv->action() & ChEnpassant))
		{
			// return the eaten piece
			mv->pieceEaten()->square()->piece(mv->pieceEaten());
			mv->pieceEaten()->setEaten(false);
		}
		
		if(mv->action() & ChPromotion)
		{
			mv->piece()->setEaten(false);
			
			// free the memory
			delete ((Pawn*)mv->piece())->promotedTo();
			((Pawn*)mv->piece())->promotedTo(NULL);
			// remove from vector
			if(mv->piece()->color() == ChWhite)
				_wPlr->promotes().pop_back();
			else
				_bPlr->promotes().pop_back();

			if(!mv->action() & ChEat)
				mv->to()->piece(NULL);
		}

		if(_trivialMovesNum > 0)
			_trivialMovesNum--;
	}


	const Move * Engine::lastMove() const
	{
		return _tracker->last();
	}

	bool Engine::isDrawn(const Move * last)
	{
		//const Move * m = _tracker->last();

		if(last->piece()->type() == ChPawn || (last->action() & ChEat))
			_trivialMovesNum = 0;
		else
			_trivialMovesNum++;

		if(_trivialMovesNum >= 50)
			return true;

		if(last->action() & (ChEat | ChEnpassant))
		{
			// see if just 2 kings remained
			for (int i = 0; i < PLR_PIECES; i++)
			{
				if((!_wPlr->getPiece(i)->isEaten() && _wPlr->getPiece(i)->type() != ChKing) ||
					(!_bPlr->getPiece(i)->isEaten() && _bPlr->getPiece(i)->type() != ChKing))
						return true;
			}
		}

		if(_whosPlaying == ChWhite ? _bPlr->canMoveAny() : _wPlr->canMoveAny())
			return true; 

		return false;
	}

	ChStatus Engine::analyzeOverallStatus(const Move * last)
	{
		ChStatus result = ChStatus::ChStatusNone;

		//Move * m = _tracker->last();

		// pick the attacker's king!
		Piece* king = _whosPlaying == ChBlack ? _bKing : _wKing;

		Piece * p = _board->underAttackBy(king->square()->row(), king->square()->column(), king->color());

		if(p != NULL) // see if attacker is not checked himself!
		{
			if(_whosPlaying == ChBlack)
				result = ChStatus::ChBlackIsChecked;
			else
				result = ChStatus::ChWhiteIsChecked;
		}

		// pick the victim's king!
		king = _whosPlaying == ChWhite ? _bKing : _wKing;

		p = _board->underAttackBy(king->square()->row(), king->square()->column(), king->color());
		
		if(p != NULL)
		{
			if(_whosPlaying == ChWhite)
				result = _bPlr->canMoveAny() ? ChStatus::ChBlackIsChecked : ChStatus::ChBlackIsCheckmate;
			else
				result = _wPlr->canMoveAny() ? ChStatus::ChWhiteIsChecked : ChStatus::ChWhiteIsCheckmate;
		}

		if(result == ChStatus::ChStatusNone && isDrawn(last))
			result = ChStatus::ChDraw;

		return result;
	}

	ChStatus Engine::analyzeMove(const Move * move)
	{
		ChStatus result = ChStatus::ChStatusNone;

		// do move
		doMove(move);

		// analyze overall status
		result = analyzeOverallStatus(move);

		// undo move !!
		undoMove(move);

		// return result
		return result;
	}

	void Engine::switchTurn()
	{
		if(_whosPlaying == ChWhite)
			_whosPlaying = ChBlack;
		else
			_whosPlaying = ChWhite;
	}

	bool Engine::hasCheckCondition() const
	{
		if(_tracker->moveCount() > 0)
			return (status() != ChStatus::ChDraw && status() != ChStatus::ChStatusNone);
		return false;
	}

	Core::ChColor Engine::whoIsChecked() const
	{
		if(this->hasCheckCondition() && _tracker->last()->piece()->color() == ChWhite)
				return ChBlack;
		return ChWhite;
	}
}
